package com.duoduo.common.util.aspectj;


import com.duoduo.common.api.constants.Constant;
import com.duoduo.common.api.exception.CreateKeyException;
import com.duoduo.common.api.exception.lock.AddLockException;
import com.duoduo.common.api.exception.lock.AddLockInterruptException;
import com.duoduo.common.api.help.JoinPointInnerUtil;
import com.duoduo.common.api.help.LockOperatorInnerUtil;
import com.duoduo.common.util.anno.method.Lock;
import com.duoduo.common.util.helper.KeyHelper;
import com.duoduo.common.util.myhelper.SpringElUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class DistributedLockAdvice {

	private static final Logger logger = LoggerFactory.getLogger(DistributedLockAdvice.class);

	@Pointcut("@annotation(com.duoduo.common.util.anno.method.Lock)")
	public void annoAspce() {
	}

	/**
	 * @param joinpoint
	 * @return
	 * @throws CreateKeyException, AddLockInterruptException, AddLockException, ReleaseLockException, UnrerentLockException
	 */
	@Around("annoAspce()")
    public <T> Object doAround(ProceedingJoinPoint joinpoint) throws Throwable {
        Object result = null;
        String methodName = JoinPointInnerUtil.getMethodName(joinpoint);
        Object[] objs = JoinPointInnerUtil.getMethodArgValues(joinpoint);
        Lock anno = JoinPointInnerUtil.getMethodAnnotation(joinpoint, Lock.class);
        String lockKey = null;
        try{
        	lockKey = KeyHelper.createKey(anno.keyRule(), joinpoint, methodName, objs);
        } catch (CreateKeyException e){
        	// 生成key失败，直接抛出异常，不再执行业务代码
        	String msg = "(distributedLock) createKey error, methodName: " + methodName;
        	logger.error(msg, e);
        	throw new CreateKeyException(msg, e);
        }
    	// 如果是null，则底层会直接用配置的appName；如果是空字符串，就会用空字符作为appName
    	String prefix = anno.keyRule().useAppName() ? null : Constant.LOCK_DEFAULT;
    	String prefixName = anno.prefixName();

    	try{
	    	try{
	    		String requestIdExpression = anno.requestId();
	    		String requestId = Constant.EMPTY_STRING;
	    		if(StringUtils.isNotEmpty(requestIdExpression)){
	    			String[] paramNames = JoinPointInnerUtil.getParamterNames(joinpoint);
	    			Object tmp = SpringElUtil.getCacheKey(objs, paramNames, requestIdExpression);
	    			if(null != tmp){
	    				requestId = tmp.toString();
	    			}
	    		}
		    	if(!LockOperatorInnerUtil.tryGetDistributedLock(prefixName, prefix, lockKey, requestId, anno.timeOut(), anno.timeOutThenExecBussiness())){
		    		// 得不到锁，直接抛出异常，不再执行业务代码
		    		throw new AddLockInterruptException("(distributedLock) add lock fail! lockKey:" + lockKey);
		    	}
	    	} catch (AddLockInterruptException e){
	    		logger.warn(e.getMessage());
	        	throw new AddLockInterruptException(e.getMessage());
	    	} catch (Exception e){
	    		String msg = "(distributedLock) add lock error! lockKey:" + lockKey;
	    		logger.error(msg, e);
	        	throw new AddLockException(msg, e);
	    	}
    		// 得到锁，执行业务
    		result = joinpoint.proceed();
    	}finally{
    		//最后一定要尝试释放锁
    		LockOperatorInnerUtil.releaseDistributedLock(prefixName, prefix, lockKey, anno.release());
    	}
    	return result;
	}

}